﻿using System;

namespace Holidays {

	public static class EasterExtension {

		/// <summary>
		/// Extension method that returns the date of Easter Sunday for the year of the given date.
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		/// <remarks>The Gauss method is used for calculation the date.</remarks>
		public static DateTime Easter(this DateTime date) {
			return date.Easter(EasterAlgorithm.Gauss);
		}

		/// <summary>
		/// Extension method that returns the date of Easter Sunday for the year of the given date using the given algoritm.
		/// </summary>
		/// <param name="date"></param>
		/// <param name="algorithm"></param>
		/// <returns></returns>
		public static DateTime Easter(this DateTime date, EasterAlgorithm algorithm) {
			Int32 year = date.Year;
			switch (algorithm) {
				case EasterAlgorithm.Gauss:
					return GaussEaster(date.Year);
				case EasterAlgorithm.MeeusJonesButcher:
					return MeeusJonesButcher(date.Year);
				default:
					throw new ApplicationException("Unknown Easter algorithm");
			}
		}

		private static DateTime MeeusJonesButcher(Int32 year) {
			Int32 a = year % 19;
			Int32 b = (Int32)Math.Floor((Decimal)year / (Decimal)100);
			Int32 c = year % 100;
			Int32 d = (Int32)Math.Floor((Decimal)b / (Decimal)4);
			Int32 e = b % 4;
			Int32 f = (Int32)Math.Floor((Decimal)(b + 8) / (Decimal)25);
			Int32 g = (Int32)Math.Floor((Decimal)(b - f + 1) / (Decimal)3);
			Int32 h = (19 * a + b - d - g + 15) % 30;
			Int32 i = (Int32)Math.Floor((Decimal)c / (Decimal)4);
			Int32 k = c % 4;
			Int32 L = (32 + 2 * e + 2 * i - h - k) % 7;
			Int32 m = (Int32)Math.Floor((Decimal)(a + 11 * h + 22 * L) / (Decimal)451);

			Int32 month = (Int32)Math.Floor((Decimal)(h + L - 7 * m + 114) / (Decimal)31);
			Int32 day = ((h + L - 7 * m + 114) % 31) + 1;

			return new DateTime(year, month, day);
		}

		private static DateTime GaussEaster(Int32 year) {
			Int32 a = year % 19;
			Int32 b = year % 4;
			Int32 c = year % 7;

			Int32 k = (Int32)Math.Floor((Decimal)year / (Decimal)100);
			Int32 p = (Int32)Math.Floor((Decimal)(13 + 8 * k) / (Decimal)25);
			Int32 q = (Int32)Math.Floor((Decimal)k / (Decimal)4);

			Int32 M = (15 - p + k - q) % 30;
			Int32 N = (4 + k - q) % 7;

			Int32 d = ((19 * a) + M) % 30;
			Int32 e = ((2 * b) + (4 * c) + (6 * d) + N) % 7;

			Int32 day = 22 + d + e;
			Int32 month = 4;

			if (day <= 31) {
				month = 3;
			}
			else if (d == 28 && e == 6 && a > 10) {
				day = 18;
			}
			else if (d == 29 && e == 6) {
				day = 19;
			}
			else {
				day = d + e - 9;
			}

			return new DateTime(year, month, day);
		}
	}
}